﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SimpleRest.Core;
using SimpleRest.Core.Routing;
using SimpleRest.Core.Extensions.Exceptions;

namespace SimpleRest.Core.Extensions.Documentation
{
    public static class DocumentationUtility
    {
        public static Options GetOptions(this Type serviceType, Configuration configuration, DocumentationHandler getOptionsFunc)
        {
            if (!serviceType.IsValidService()) throw new Exception("Service type specified must be a valid service.");
            return getOptionsFunc.Invoke(serviceType, configuration);
        }

        public static Options GetOptions(this Type serviceType, Configuration configuration)
        {
            if (!serviceType.IsValidService()) throw new Exception("Service type specified must be a valid service.");

            Options options = new Options();
            List<Resource> resources = new List<Resource>();
            bool isGeneralDescriptionFound = false;

            var serviceRoutes = RouteUtility.GetRegisteredServiceRoutes(serviceType);
            var services = serviceRoutes.Select(d => d.ServiceType).ToList();
            foreach (var mediaType in configuration.MediaTypeFormatters)
            {
                if (options.MediaTypeFormatters == null) options.MediaTypeFormatters = new List<MediaTypeFormatter>();
                if (options.MediaTypeFormatters == null) continue;

                string mediaTypeDescription = null;
                var attributes = mediaType.GetType().GetCustomAttributes(true);
                foreach (object attr in attributes)
                {
                    if (attr is GeneralDescriptionAttribute)
                    {
                        mediaTypeDescription = (attr as GeneralDescriptionAttribute).Description;
                        continue;
                    }
                }

                options.MediaTypeFormatters.Add(new MediaTypeFormatter()
                {
                    Description = mediaTypeDescription,
                    SupportedMediaTypes = mediaType.SupportedMediaTypes.ToList()
                });
            }

            foreach (var service in services)
            {
                foreach (var attr in service.GetCustomAttributes(true))
                {
                    if (attr is GeneralDescriptionAttribute)
                    {
                        if (isGeneralDescriptionFound)
                            throw new GeneralDescriptionException(RouteUtility.GetRoutePrefix(service));
                        else
                            isGeneralDescriptionFound = true;

                        options.Description = (attr as GeneralDescriptionAttribute).Description;
                    }
                    if (attr is LinkDescriptionAttribute)
                    {
                        if (options.Links == null) options.Links = new List<Link>();
                        options.Links.Add((attr as LinkDescriptionAttribute).GetLink());
                    }
                    if (attr is StatusCodeDescriptionAttribute)
                    {
                        if (options.HttpStatusCodes == null) options.HttpStatusCodes = new List<HttpStatusCode>();
                        options.HttpStatusCodes.Add((attr as StatusCodeDescriptionAttribute).GetValue());
                    }
                }
                    string previousUriTemplate = "";
                    Resource resource = null;
                     
                    var operations = RouteUtility.GetOperations(service).OrderBy(d => d.UriTemplate.Trim(new char[] { '/' })).ToList();
                    foreach (var operation in operations)
                    {
                        if (!previousUriTemplate.Split(new char[] { '?' })[0].Trim(new char[] { '/' }).ToLower().Equals(operation.UriTemplate.Split(new char[] { '?' })[0].Trim(new char[] { '/' }).ToLower()))
                        {
                            if (resource != null)
                                resources.Add(resource);
                            resource = new Resource();
                            resource.HttpMethods = new List<HttpMethod>();
                        }

                        string fullUri = "/" + operation.UriTemplate.Trim(new char[] { '/' });
                        resource.BaseUri = "/" + fullUri.Split(new char[] { '?' })[0].Trim(new char[] { '/' });
                        resource.XsdUri = 
                            configuration != null && configuration is ExtendedConfiguration && (configuration as ExtendedConfiguration).IsAutoXsdRoutingEnabled ? resource.BaseUri + ".XSD" : null; // ...?  Dirty.  TODO: Find better way
                        resource.SampleResponseUri = 
                            configuration != null && configuration is ExtendedConfiguration && (configuration as ExtendedConfiguration).IsSampleResponseEnabled ? resource.BaseUri + ".SAMPLE" : null; // ...? Dirty.  TODO: Find better way

                        string httpMethodDescription = "";
                        List<HttpStatusCode> httpMethodErrorCodes = new List<HttpStatusCode>();
                        List<Link> httpMethodLinks = new List<Link>();
                        List<Parameter> httpMethodParameters = new List<Parameter>();

                        foreach (var methodAttr in operation.TargetMethod.GetCustomAttributes(true))
                        {
                            if (methodAttr is GeneralDescriptionAttribute)
                                httpMethodDescription = (methodAttr as GeneralDescriptionAttribute).Description;
                            if (methodAttr is StatusCodeDescriptionAttribute)
                                httpMethodErrorCodes.Add((methodAttr as StatusCodeDescriptionAttribute).GetValue());
                            if (methodAttr is LinkDescriptionAttribute)
                                httpMethodLinks.Add((methodAttr as LinkDescriptionAttribute).GetLink());
                            if (methodAttr is ParameterDescriptionAttribute)
                                httpMethodParameters.Add((methodAttr as ParameterDescriptionAttribute).GetParameter());
                        }

                        resource.HttpMethods.Add(new HttpMethod()
                        {
                            Description = httpMethodDescription,
                            HttpStatusCodes = httpMethodErrorCodes,
                            Parameters = httpMethodParameters,
                            Links = httpMethodLinks,
                            Verb = operation.HttpMethod,
                            FullUri = fullUri
                        });

                        previousUriTemplate = operation.UriTemplate;
                    }
                    resources.Add(resource);
                    options.Resources = resources;
                
            }

            return options;
        }
    }
}
